package com.miniapp.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.miniapp.entity.User;
import com.miniapp.handler.exception.BusinessException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author author
 * @date 2025-09-01 11:02
 */
@Component
public class MiniAppUtil {

    @Value("${wechat.miniapp.appid}")
    private String appid;

    @Value("${wechat.miniapp.secret}")
    private String secret;

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Value("${jwt.expire-time:7200000}")
    private long expireTime;

    /**
     * getSessionInfo 方法中增加缓存
     */
    private final Map<String, Map<String, String>> codeCache = new ConcurrentHashMap<>();


    /**
     * 调用微信接口获取session_key和openid
     */
    public Map<String, String> getSessionInfo(String code) {
        if (StrUtil.isEmpty(code)) {
            throw new BusinessException("code不能为空");
        }

        // 检查 code 是否已使用
        if (codeCache.containsKey(code)) {
            // 返回缓存的结果，避免重复调用微信接口
            return codeCache.get(code);
        }

        String url = "https://api.weixin.qq.com/sns/jscode2session";
        Map<String, Object> params = new HashMap<>(4);
        params.put("appid", appid);
        params.put("secret", secret);
        params.put("js_code", code);
        params.put("grant_type", "authorization_code");

        String result = HttpUtil.get(url, params);
        JSONObject json = JSONUtil.parseObj(result);

        // 处理微信接口错误
        if (json.containsKey("errcode") && json.getInt("errcode") != 0) {
            throw new BusinessException(
                    "微信登录失败：" + json.getInt("errcode") + "，" + json.getStr("errmsg")
            );
        }

        Map<String, String> sessionInfo = new HashMap<>(2);
        sessionInfo.put("openid", json.getStr("openid"));
        sessionInfo.put("session_key", json.getStr("session_key"));
        sessionInfo.put("unionid", json.getStr("unionid"));

        // 缓存结果（设置5分钟过期，与code有效期一致）
        codeCache.put(code, sessionInfo);
        new Thread(() -> {
            try {
                Thread.sleep((long)5 * 60 * 1000); // 5分钟后清除缓存
                codeCache.remove(code);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
        return sessionInfo;
    }

    /**
     * 解密微信手机号
     */
    public String decryptPhone(String encryptedData, String sessionKey, String iv) {
        if (StrUtil.isEmpty(encryptedData) || StrUtil.isEmpty(sessionKey) || StrUtil.isEmpty(iv)) {
            throw new BusinessException("解密参数不完整");
        }

        try {
            // Base64解码
            byte[] sessionKeyBytes = Base64.decode(sessionKey);
            byte[] encryptedDataBytes = Base64.decode(encryptedData);
            byte[] ivBytes = Base64.decode(iv);

            // 初始化AES-CBC加密器
            SecretKeySpec keySpec = new SecretKeySpec(sessionKeyBytes, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
            SymmetricCrypto aes = new SymmetricCrypto("AES/CBC/PKCS7Padding", keySpec, ivSpec);

            // 解密并解析手机号
            String decrypted = aes.decryptStr(encryptedDataBytes, StandardCharsets.UTF_8);
            JSONObject json = JSONUtil.parseObj(decrypted);
            return json.getStr("phoneNumber");
        } catch (Exception e) {
            throw new BusinessException("手机号解密失败：" + e.getMessage());
        }
    }

    /**
     * 生成JWT令牌
     */
    public String generateToken(User user) {
        // 设置JWT payload
        Map<String, Object> payload = new HashMap<>(5);
        payload.put("userId", user.getId());
        payload.put("openId", user.getOpenId());
        payload.put("role", user.getRole());
        // 过期时间戳(毫秒)
        payload.put("exp", System.currentTimeMillis() + expireTime);
        payload.put("jti", UUID.randomUUID().toString());

        // 使用Hutool生成JWT
        return cn.hutool.jwt.JWTUtil.createToken(payload, jwtSecret.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 验证令牌（适配低版本Hutool）
     */
    public Map<String, Object> verifyToken(String token) {
        if (token == null || token.trim().isEmpty()) {
            throw new BusinessException("令牌不能为空");
        }

        try {
            byte[] keyBytes = jwtSecret.getBytes(StandardCharsets.UTF_8);
            JWT jwt = JWTUtil.parseToken(token).setKey(keyBytes);

            // 验证签名
            if (!jwt.verify()) {
                throw new BusinessException("无效的令牌：签名错误");
            }

            // 验证过期时间
            Object expObj = jwt.getPayload("exp");
            if (expObj == null) {
                throw new BusinessException("令牌缺少过期时间");
            }
            long expTime = Long.parseLong(expObj.toString());
            if (System.currentTimeMillis() > expTime) {
                throw new BusinessException("登录已过期，请重新登录");
            }

            return jwt.getPayloads();

        } catch (NumberFormatException e) {
            throw new BusinessException("令牌格式错误：过期时间不是数字");
        } catch (Exception e) {
            throw new BusinessException("令牌验证失败：" + e.getMessage());
        }
    }

    /**
     * 提取用户信息（保持不变）
     */
    public User extractUserFromPayload(Map<String, Object> payload) {
        try {
            Long userId = Long.parseLong(payload.get("userId").toString());
            String openId = payload.get("openId").toString();
            String role = payload.get("role") != null ? payload.get("role").toString() : "USER";

            User user = new User();
            user.setId(userId);
            user.setOpenId(openId);
            user.setRole(role);
            return user;
        } catch (Exception e) {
            throw new BusinessException("令牌载荷解析失败：" + e.getMessage());
        }
    }

}
